package com.yc.spring.aop;

import com.yc.spring.AopConfig;
import com.yc.spring.ioc.AnnotationConfigApplicationContext;
import com.yc.spring.vote.web.VoteAction;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class AOPContext extends AnnotationConfigApplicationContext {

    public Map<String, AspectInfo> aspectInfoMap = new HashMap<>();

    public AOPContext(Class<?> configClass) throws Exception {
        super(configClass);
        // AOP 在 IOC 初始化完成之后 ...

        // 1. 查找组件中的切面类 => @Aspect 注解
        List<Object> aspects = new ArrayList<>();
        Collection<BeanInfo> values = ioc.values();
        for (BeanInfo value : values) {
            Aspect aspect = value.bean.getClass().getAnnotation(Aspect.class);
            if (aspect != null) {
                aspects.add(value.bean);
            }
        }

        // 2. 统计切点与增强 Pointcut + Advice, 封装切面信息对象, 保存切面集合中
        for (Object aspect : aspects) {
            Method[] methods = aspect.getClass().getDeclaredMethods();
            for (Method method : methods) {
                // 只统计 Before 增强
                Before before = method.getAnnotation(Before.class);
                if (before != null) {
                    String pointcut = before.value();
                    // 只判断 execution 格式
                    if(! pointcut.startsWith("execution")){
                        // pointcut => myPointcut()
                        String methodName = pointcut.replaceAll("(\\w+).+","$1");
                        // 获取切点方法
                        Method pointcutMethod = aspect.getClass().getDeclaredMethod(methodName);
                        // 获取切点方法上的注解
                        Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                        // 对切点重新赋值
                        pointcut = pointcutAnno.value();
                    }
                    AspectInfo aspectInfo = aspectInfoMap.get(pointcut);
                    if(aspectInfo==null){
                        aspectInfo = new AspectInfo();
                        // 保存切点
                        aspectInfo.poiontcut = pointcut;
                        // 保存切面对象
                        aspectInfo.aspectObject = aspect;
                        aspectInfoMap.put(pointcut, aspectInfo);
                    }
                    // 将该前置增强方法添加到集合中
                    aspectInfo.befores.add(method);
                }
            }
        }

        Map<BeanInfo, AspectInfo> aopBeans = new HashMap<>();
        // 3. 扫描所有的组件, 查看是否包含被切点拦截的方法
        for (BeanInfo beanInfo : ioc.values()) {
            for (Method method : beanInfo.bean.getClass().getDeclaredMethods()) {
                // 构建方法表达式
                String s = method.toString();
                String methodEl = s.replaceAll("(public\\s|protected\\s|private\\s)?(.+)","$2");
                // 使用切点表达式 判断 该方法是否被拦截
                aspectInfoMap.forEach((pointcut, aspectInfo)->{
                    // 只处理 execution 表达式
                    String el = pointcut.replaceAll("execution\\((.+)\\)","$1");
                    el = el.replaceAll("\\(","\\\\(");
                    el = el.replaceAll("\\)","\\\\)");
                    el = el.replaceAll("\\*", "\\\\w*");
                    // TODO .. 未完成判断
                    if(methodEl.matches(el)){
                        aopBeans.put(beanInfo, aspectInfo);
                    }
                });
            }
        }

        // 4. 如果组件包含被切点拦截的方法, 那么生成动态代理对象替换原有的组件
        aopBeans.forEach((aopBean, aspectInfo)->{
            // 1. 判断 aopBean 是否实现接口
            Class<?>[] interfaces = aopBean.bean.getClass().getInterfaces();
            if (interfaces==null || interfaces.length==0){
                // 2. 未实现 => CGLIB动态代理
                cglibProxy(aopBean, aspectInfo);
            } else {
                // 3. 否则 =>jdk动态代理
                jdkProxy(aopBean, aspectInfo);
            }
        });

        // 5. 对代理对象再进行一次自动装配

    }

    private void jdkProxy(BeanInfo beanInfo, AspectInfo aspectInfo) {
    }

    private void cglibProxy(BeanInfo beanInfo, AspectInfo aspectInfo) {
        Enhancer enhancer = new Enhancer();
        // 父类
        enhancer.setSuperclass(beanInfo.bean.getClass());
        // 回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                // 判断该方法是否要被增强
                // 构建方法表达式
                String s = method.toString();
                String methodEl = s.replaceAll("(public\\s|protected\\s|private\\s)?(.+)","$2");
                // 定义切面对象
                AspectInfo[] aspectInfo1 = new AspectInfo[1];
                aspectInfoMap.forEach((pointcut, aspectInfo)->{
                    // 只处理 execution 表达式
                    String el = pointcut.replaceAll("execution\\((.+)\\)","$1");
                    el = el.replaceAll("\\(","\\\\(");
                    el = el.replaceAll("\\)","\\\\)");
                    el = el.replaceAll("\\*", "\\\\w*");
                    // 判断该方法是否要代理
                    if(methodEl.matches(el)){
                        aspectInfo1[0] = aspectInfo;
                    }
                });

                // 定义连接点, 封装方法相关属性
                JoinPoint jp = new JoinPoint() {
                    @Override
                    public String toShortString() {
                        return method.getName();
                    }

                    @Override
                    public String toLongString() {
                        return null;
                    }

                    @Override
                    public Object getThis() {
                        return null;
                    }

                    @Override
                    public Object getTarget() {
                        return null;
                    }

                    @Override
                    public Object[] getArgs() {
                        return objects;
                    }

                    @Override
                    public Signature getSignature() {
                        return null;
                    }

                    @Override
                    public SourceLocation getSourceLocation() {
                        return null;
                    }

                    @Override
                    public String getKind() {
                        return null;
                    }

                    @Override
                    public StaticPart getStaticPart() {
                        return null;
                    }
                };

                // 切点切到该方法
                if(aspectInfo1[0]!=null && aspectInfo1[0].befores.size() > 0){
                    // 执行前置增强方法
                    aspectInfo1[0].befores.forEach(before->{
                        try {
                            before.invoke(aspectInfo1[0].aspectObject, jp);
                        } catch (Exception e) {
                            throw new RuntimeException("前置增强方法执行错误!", e);
                        }
                    });
                }
                // 调用业务方法
                Object res = methodProxy.invoke(beanInfo.bean, objects);

                // 返回业务方法的结构
                if(aspectInfo1[0]!=null && aspectInfo1[0].afterReturnings.size() > 0) {
                    // 执行返回增强增强方法
                    aspectInfo1[0].afterReturnings.forEach(afterReturning -> {
                        try {
                            afterReturning.invoke(aspectInfo1[0].aspectObject, jp);
                        } catch (Exception e) {
                            throw new RuntimeException("前置增强方法执行错误!", e);
                        }
                    });
                }
                return res;
            }
        });
        Object proxyBean = enhancer.create();
        // 替换被代理对象
        beanInfo.bean = proxyBean;
    }

    public static class AspectInfo {
        // 切点
        String poiontcut;
        // 增强方法集合
        List<Method> befores = new ArrayList<>();
        List<Method> afters = new ArrayList<>();
        List<Method> afterReturnings = new ArrayList<>();
        List<Method> afterThrowings = new ArrayList<>();
        List<Method> arounds = new ArrayList<>();
        Object aspectObject;

        @Override
        public String toString() {
            return "AspectInfo{" +
                    "poiontcut='" + poiontcut + '\'' +
                    ", befores=" + befores +
                    ", afters=" + afters +
                    ", afterReturnings=" + afterReturnings +
                    ", afterThrowings=" + afterThrowings +
                    ", arounds=" + arounds +
                    '}';
        }
    }


    public static void main(String[] args) throws Exception {
        AOPContext ctx = new AOPContext(AopConfig.class);
        ctx.aspectInfoMap.forEach((k,v)->{
            System.out.println(v);
        });

        System.out.println("---------------------------------------");
        VoteAction voteAction = (VoteAction) ctx.getBean("voteAction");
        voteAction.test();


//        Object a = 123;
//        test(a);
//        System.out.println(a);

    }

    public static void test(Object obj){
        obj = "abc";
    }
}

